Skip to content

Comments

Fix #4656 - E+ 22.2.0: Wrap Chiller:Electric:ASHRAE205#4687

Merged
jmarrec merged 24 commits intov22.2.0-IOFreezefrom
4656_ChillerElectricASHRAE205
Sep 28, 2022
Merged

Fix #4656 - E+ 22.2.0: Wrap Chiller:Electric:ASHRAE205#4687
jmarrec merged 24 commits intov22.2.0-IOFreezefrom
4656_ChillerElectricASHRAE205

Conversation

@jmarrec
Copy link
Collaborator

@jmarrec jmarrec commented Sep 19, 2022

Pull request overview

Pull Request Author

  • Model API Changes / Additions
  • Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
  • Model API methods are tested (in src/model/test)
  • EnergyPlus ForwardTranslator Tests (in src/energyplus/Test)
  • If a new object or method, added a test in NREL/OpenStudio-resources: Fix #4656 - E+ 22.2.0: Wrap Chiller:Electric:ASHRAE205 OpenStudio-resources#179
  • If needed, added VersionTranslation rules for the objects (src/osversion/VersionTranslator.cpp)
  • Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
  • All new and existing tests passes
  • If methods have been deprecated, update rest of code to use the new methods: N/A

Labels:

  • If change to an IDD file, add the label IDDChange
  • If breaking existing API, add the label APIChange
  • If deemed ready, add label Pull Request - Ready for CI so that CI builds your PR

Review Checklist

This will not be exhaustively relevant to every PR.

  • Perform a Code Review on GitHub
  • Code Style, strip trailing whitespace, etc.
  • All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
  • Labeling is ok
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified

```
$os_build3/Products/openstudio GenerateClass.rb -c "ChillerElectricASHRAE205" -b "WaterToWaterComponent"  -i "OS:Chiller:Electric:ASHRAE205" -s model -o /media/DataExt4/Software/Others/OpenStudio3/src/model/ -p -f -r
```
… a Node

OutdoorAir:Node isn't wrapped in OS SDK
…ated, a Space is returned not a ThermalZone!
@jmarrec jmarrec changed the base branch from develop to v22.2.0-IOFreeze September 19, 2022 13:21
Comment on lines +42360 to +42479
Chiller:Electric:ASHRAE205,
\min-fields 11
\memo This chiller model utilizes ASHRAE Standard 205 compliant representations
\memo for chillers (Representation Specification RS0001).
A1, \field Name
\type alpha
\reference Chillers
\required-field
\reference-class-name validPlantEquipmentTypes
\reference validPlantEquipmentNames
\reference-class-name validBranchEquipmentTypes
\reference validBranchEquipmentNames
A2, \field Representation File Name
\note The name of the ASHRAE205 RS0001 (chiller) representation file
\type alpha
\retaincase
\required-field
A3, \field Performance Interpolation Method
\type choice
\key Linear
\key Cubic
\default Linear
N1, \field Rated Capacity
\note Not yet implemented / reserved for future use. Full load capacity at AHRI 550/590 test conditions.
\note Used to scale representation data.
\type real
\units W
\minimum> 0.0
\autosizable
\default autosize
N2, \field Sizing Factor
\note Multiplies the autosized flow rates.
\type real
\minimum> 0.0
\default 1.0
A4 , \field Ambient Temperature Indicator
\note Used to determine standby losses
\required-field
\type choice
\key Schedule
\key Zone
\key Outdoors
A5 , \field Ambient Temperature Schedule Name
\type object-list
\object-list ScheduleNames
A6, \field Ambient Temperature Zone Name
\note Any energy imbalance on the chiller results in heat added to this zone.
\type object-list
\object-list ZoneNames
A7, \field Ambient Temperature Outdoor Air Node Name
\type node
\note required for Ambient Temperature Indicator=Outdoors
A8, \field Chilled Water Inlet Node Name
\type node
\required-field
A9, \field Chilled Water Outlet Node Name
\type node
\required-field
A10, \field Chilled Water Maximum Requested Flow Rate
\type real
\units m3/s
\minimum> 0
\autosizable
\ip-units gal/min
\default autosize
A11, \field Condenser Inlet Node Name
\type node
A12, \field Condenser Outlet Node Name
\type node
A13, \field Condenser Maximum Requested Flow Rate
\type real
\units m3/s
\minimum> 0
\autosizable
\ip-units gal/min
\default autosize
A14, \field Chiller Flow Mode
\note Select operating mode for fluid flow through the chiller. "NotModulated" is for
\note either variable or constant pumping with flow controlled by the external plant system.
\note "ConstantFlow" is for constant pumping with flow controlled by chiller to operate at
\note full design flow rate. "LeavingSetpointModulated" is for variable pumping with flow
\note controlled by chiller to vary flow to target a leaving temperature setpoint.
\type choice
\key ConstantFlow
\key LeavingSetpointModulated
\key NotModulated
\default NotModulated
A15, \field Oil Cooler Inlet Node Name
\note Use if the oil cooler uses an external cooling loop, otherwise the oil cooler will add
\note heat to the ambient conditions (i.e., it is air cooled).
\type node
A16, \field Oil Cooler Outlet Node Name
\type node
A17, \field Oil Cooler Design Flow Rate
\type real
\units m3/s
\minimum> 0
\ip-units gal/min
A18, \field Auxiliary Inlet Node Name
\note Use if the auxiliary components of the chiller use an external cooling loop, otherwise
\note the auxiliary components will add heat to the ambient conditions (i.e., they are air cooled).
\type node
A19, \field Auxiliary Outlet Node Name
\type node
A20, \field Auxiliary Cooling Design Flow Rate
\type real
\units m3/s
\minimum> 0
\ip-units gal/min
A21, \field Heat Recovery Inlet Node Name
\note Not yet implemented / reserved for future use. Heat recovery is not yet within scope of ASHRAE Stanard 205.
\type node
A21, \field Heat Recovery Outlet Node Name
\note Not yet implemented / reserved for future use. Heat recovery is not yet within scope of ASHRAE Stanard 205.
\type node
A23; \field End-Use Subcategory
\note Any text may be used here to categorize the end-uses in the ABUPS End Uses by Subcategory table.
\type alpha
\retaincase
\default General
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New IDD object. Everything is required-field as much as possible.

NOTE:

  • This object will eventually have FIVE loops: chilled water, condenser, heat recovery, oil cooler, and auxiliary
  • Heat Recovery is reserved as a Tertiary connection, but it's disabled right now in model SDK due to the fact that E+ doesn't implement it yet.

Comment on lines +71 to +100
// chilledWaterLoop
virtual boost::optional<PlantLoop> plantLoop() const override;
virtual unsigned supplyInletPort() const override;
virtual unsigned supplyOutletPort() const override;

// condenserWaterLoop
virtual boost::optional<PlantLoop> secondaryPlantLoop() const override;
virtual unsigned demandInletPort() const override;
virtual unsigned demandOutletPort() const override;

// heatRecoveryLoop
virtual boost::optional<PlantLoop> tertiaryPlantLoop() const override;
virtual unsigned tertiaryInletPort() const override;
virtual unsigned tertiaryOutletPort() const override;

virtual std::vector<HVACComponent> edges(const boost::optional<HVACComponent>& prev) override;

/** This function will perform a check if trying to add it to a node that is on the demand side of a plant loop.
* If:
* - the node is on the demand side of a loop
* - the node isn't on the current condenser water loop itself
* - the chiller doesn't already have a heat recovery (tertiary) loop,
* then it tries to add it to the Tertiary loop.
* In all other cases, it will call the base class' method WaterToWaterComponent_Impl::addToNode()
* If this is connecting to the demand side of a loop (not tertiary), will set the chiller condenserType to WaterCooled
*/
virtual bool addToNode(Node& node) override;

/* Restricts addToTertiaryNode to a node that is on the demand side of a plant loop (tertiary = Heat Recovery Loop) */
virtual bool addToTertiaryNode(Node& node) override;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to override pretty much everything due to the fact that this is the first object with more than 3 loops...

Comment on lines +492 to +513
bool ChillerElectricASHRAE205_Impl::addToTertiaryNode(Node& node) {

if constexpr (!ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) {
LOG(Warn, "For " << briefDescription() << ", Heat Recovery isn't implemented by EnergyPlus yet, so tertiary connection is disabled.");
return false;
}

auto t_plantLoop = node.plantLoop();

// Only accept adding to a node that is on a demand side of a plant loop
// Since tertiary here = heat recovery loop (heating)
if (t_plantLoop) {
if (t_plantLoop->demandComponent(node.handle())) {
// Call base class method which accepts both supply and demand
return WaterToWaterComponent_Impl::addToTertiaryNode(node);
} else {
LOG(Info,
"Tertiary Loop (Heat Recovery Loop) connections can only be placed on the Demand side (of a Heating Loop), for " << briefDescription());
}
}
return false;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabled for now, but I have done that last to make sure it works correctly anyways.

Comment on lines +515 to +555
std::vector<HVACComponent> ChillerElectricASHRAE205_Impl::edges(const boost::optional<HVACComponent>& prev) {
// This handles supply, demand, and tertiary connections
std::vector<HVACComponent> edges = WaterToWaterComponent_Impl::edges(prev);

auto pushOilCoolerOutletModelObject = [&]() {
if (auto edgeModelObject = oilCoolerOutletModelObject()) {
auto edgeHVACComponent = edgeModelObject->optionalCast<HVACComponent>();
OS_ASSERT(edgeHVACComponent);
edges.push_back(edgeHVACComponent.get());
}
};

auto pushAuxiliaryOutletModelObject = [&]() {
if (auto edgeModelObject = auxiliaryOutletModelObject()) {
auto edgeHVACComponent = edgeModelObject->optionalCast<HVACComponent>();
OS_ASSERT(edgeHVACComponent);
edges.push_back(edgeHVACComponent.get());
}
};

if (prev) {
if (auto inletModelObject = oilCoolerInletModelObject()) {
if (prev.get() == inletModelObject.get()) {
pushOilCoolerOutletModelObject();
return edges;
}
}
if (auto inletModelObject = auxiliaryInletModelObject()) {
if (prev.get() == inletModelObject.get()) {
pushAuxiliaryOutletModelObject();
return edges;
}
}
} else {
pushOilCoolerOutletModelObject();
pushAuxiliaryOutletModelObject();
return edges;
}

return edges;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to reimplement edges to handle the extra loops

Comment on lines +566 to +573
boost::optional<PlantLoop> ChillerElectricASHRAE205_Impl::secondaryPlantLoop() const {
if (boost::optional<ModelObject> mo_ = demandOutletModelObject()) {
if (boost::optional<Node> n_ = mo_->optionalCast<Node>()) {
return n_->plantLoop();
}
}
return boost::none;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to override this one, because by default WaterToWaterComponent will just find a PlantLoop that has it on demand and is not the tertiary... But this object can be on the demand side of FOUR loops

Comment on lines +151 to +158
// Heat Recovery
if (auto node_ = modelObject.heatRecoveryInletNode()) {
idfObject.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName, node_->nameString());
}

if (auto node_ = modelObject.heatRecoveryOutletNode()) {
idfObject.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName, node_->nameString());
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaving this but it'll never be used for now (since the Heat Recovery connection is disabled at model time)

Comment on lines +111 to +122
// Ambient Temperature Zone Name: Optional Object
if (auto wo_z_ = workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName)) {
if (auto mo_ = translateAndMapWorkspaceObject(wo_z_.get())) {
// Zone is translated, and a Space is returned instead
if (boost::optional<Space> space_ = mo_->optionalCast<Space>()) {
if (auto z_ = space_->thermalZone()) {
modelObject.setAmbientTemperatureZone(z_.get());
}
} else {
LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Ambient Temperature Zone Name'");
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I messed up and didn't realize that translateAndMapWorkspaceObject for a Zone returns a Space.

Comment on lines +95 to +102
if (auto mo_ = translateAndMapWorkspaceObject(*target)) {
// Zone is translated, and a Space is returned instead
if (boost::optional<Space> space_ = mo_->optionalCast<Space>()) {
if (auto z_ = space_->thermalZone()) {
dx.setCondenserZone(z_.get());
}
} else {
LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Condenser Zone Name'");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this same issue in a bunch of existing RT files so I fixed those I found.

Comment on lines +87 to +314
/* Ensures that the nodes that translated correctly
* that means correct node names in the ChillerElectricASHRAE205 but also
* on the Branches
*/
TEST_F(EnergyPlusFixture, ForwardTranslator_ChillerElectricASHRAE205) {

ForwardTranslator ft;

Model m;

auto createLoop = [&m](const std::string& prefix) {
PlantLoop p(m);
static constexpr std::array<std::string_view, 10> compNames = {
"Supply Inlet", "Supply Splitter", "Supply Connection Node", "Supply Mixer", "Supply Outlet",
"Demand Inlet", "Demand Splitter", "Demand Connection Node", "Demand Mixer", "Demand Outlet",
};
p.setName(prefix);
for (size_t i = 0; auto& comp : p.components()) {
comp.setName(prefix + " " + std::string{compNames[i++]});
}
return p;
};

openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor");
EXPECT_TRUE(exists(p));

boost::optional<ExternalFile> representationFile = ExternalFile::getExternalFile(m, openstudio::toString(p));
ASSERT_TRUE(representationFile);

ChillerElectricASHRAE205 ch(representationFile.get());

EXPECT_TRUE(ch.setPerformanceInterpolationMethod("Cubic"));
ch.autosizeRatedCapacity();
EXPECT_TRUE(ch.setSizingFactor(1.1));

ThermalZone z(m);
z.setName("Basement");
{
std::vector<Point3d> sPoints{{0, 0, 0}, {0, 1, 0}, {1, 1, 0}, {1, 0, 0}};
auto space_ = Space::fromFloorPrint(sPoints, 3.0, m);
ASSERT_TRUE(space_);
EXPECT_TRUE(space_->setThermalZone(z));
}

EXPECT_TRUE(ch.setAmbientTemperatureZone(z));

EXPECT_TRUE(ch.setChilledWaterMaximumRequestedFlowRate(0.0428));

auto chwLoop = createLoop("chwLoop");
EXPECT_TRUE(chwLoop.addSupplyBranchForComponent(ch));

auto cndLoop = createLoop("cndLoop");
EXPECT_TRUE(cndLoop.addDemandBranchForComponent(ch));
EXPECT_TRUE(ch.setCondenserMaximumRequestedFlowRate(0.0552));

EXPECT_TRUE(ch.setChillerFlowMode("ConstantFlow"));

auto ocLoop = createLoop("ocLoop");
EXPECT_TRUE(ch.addDemandBranchOnOilCoolerLoop(ocLoop));
EXPECT_TRUE(ch.setOilCoolerDesignFlowRate(0.001));

auto auxLoop = createLoop("auxLoop");
EXPECT_TRUE(ch.addDemandBranchOnAuxiliaryLoop(auxLoop));
EXPECT_TRUE(ch.setAuxiliaryCoolingDesignFlowRate(0.002));

EXPECT_TRUE(ch.setEndUseSubcategory("Chiller"));

ch.chilledWaterInletNode()->setName("ChilledWater Inlet Node");
ch.chilledWaterOutletNode()->setName("ChilledWater Outlet Node");
ch.condenserInletNode()->setName("Condenser Inlet Node");
ch.condenserOutletNode()->setName("Condenser Outlet Node");

ch.oilCoolerInletNode()->setName("OilCooler Inlet Node");
ch.oilCoolerOutletNode()->setName("OilCooler Outlet Node");
ch.auxiliaryInletNode()->setName("Auxiliary Inlet Node");
ch.auxiliaryOutletNode()->setName("Auxiliary Outlet Node");

if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) {
auto hrLoop = createLoop("hrLoop");
EXPECT_TRUE(hrLoop.addDemandBranchForComponent(ch, true));
ch.heatRecoveryInletNode()->setName("HeatRecovery Inlet Node");
ch.heatRecoveryOutletNode()->setName("HeatRecovery Outlet Node");
}

{
Workspace w = ft.translateModel(m);

std::vector<WorkspaceObject> woChs = w.getObjectsByType(IddObjectType::Chiller_Electric_ASHRAE205);
ASSERT_EQ(1, woChs.size());
auto& woCh = woChs.front();

EXPECT_EQ(ch.nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::Name).get());
EXPECT_EQ(representationFile->filePath(), woCh.getString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName).get());
EXPECT_EQ("Cubic", woCh.getString(Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod).get());
EXPECT_EQ("Autosize", woCh.getString(Chiller_Electric_ASHRAE205Fields::RatedCapacity).get());
EXPECT_EQ(1.1, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::SizingFactor).get());

EXPECT_EQ("Zone", woCh.getString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator).get());
EXPECT_TRUE(woCh.isEmpty(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName));
EXPECT_EQ("Basement", woCh.getString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName).get());
EXPECT_TRUE(woCh.isEmpty(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName));

EXPECT_EQ(0.0428, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate).get());

EXPECT_EQ(0.0552, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate).get());

EXPECT_EQ("ConstantFlow", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChillerFlowMode).get());

EXPECT_EQ(0.001, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate).get());

EXPECT_EQ(0.002, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate).get());

EXPECT_EQ("Chiller", woCh.getString(Chiller_Electric_ASHRAE205Fields::EndUseSubcategory).get());

EXPECT_EQ("ChilledWater Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName).get());
EXPECT_EQ("ChilledWater Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName).get());
EXPECT_EQ(ch.chilledWaterInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName).get());
EXPECT_EQ(ch.chilledWaterOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName).get());

EXPECT_EQ("Condenser Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName).get());
EXPECT_EQ("Condenser Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName).get());
EXPECT_EQ(ch.condenserInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName).get());
EXPECT_EQ(ch.condenserOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName).get());

EXPECT_EQ("OilCooler Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName).get());
EXPECT_EQ("OilCooler Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName).get());
EXPECT_EQ(ch.oilCoolerInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName).get());
EXPECT_EQ(ch.oilCoolerOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName).get());

EXPECT_EQ("Auxiliary Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName).get());
EXPECT_EQ("Auxiliary Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName).get());
EXPECT_EQ(ch.auxiliaryInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName).get());
EXPECT_EQ(ch.auxiliaryOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName).get());

if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) {
EXPECT_EQ("HeatRecovery Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName).get());
EXPECT_EQ("HeatRecovery Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName).get());
EXPECT_EQ(ch.heatRecoveryInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName).get());
EXPECT_EQ(ch.heatRecoveryOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName).get());
}

// Check node names on supply/demand branches
// Checks that the special case implemented in ForwardTranslatePlantLoop::populateBranch does the right job

struct Expected
{
bool isSupply = true;
std::string plantName;
std::string inletNodeName;
std::string outletNodeName;
};

std::vector<Expected> expecteds = {
{true, ch.chilledWaterLoop()->nameString(), ch.chilledWaterInletNode()->nameString(), ch.chilledWaterOutletNode()->nameString()},
{false, ch.condenserWaterLoop()->nameString(), ch.condenserInletNode()->nameString(), ch.condenserOutletNode()->nameString()},
{false, ch.oilCoolerLoop()->nameString(), ch.oilCoolerInletNode()->nameString(), ch.oilCoolerOutletNode()->nameString()},
{false, ch.auxiliaryLoop()->nameString(), ch.auxiliaryInletNode()->nameString(), ch.auxiliaryOutletNode()->nameString()},
};

if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) {
expecteds.emplace_back(false, ch.heatRecoveryLoop()->nameString(), ch.heatRecoveryInletNode()->nameString(),
ch.heatRecoveryOutletNode()->nameString());
}

for (const auto& e : expecteds) {
auto p_ = w.getObjectByTypeAndName(IddObjectType::PlantLoop, e.plantName);
ASSERT_TRUE(p_.is_initialized()) << "Cannot find PlantLoop named " << e.plantName;
WorkspaceObject idf_plant = p_.get();
unsigned index = e.isSupply ? PlantLoopFields::PlantSideBranchListName : PlantLoopFields::DemandSideBranchListName;
WorkspaceObject idf_brlist = idf_plant.getTarget(index).get();

// Should have at least three branches: supply inlet, the one with the Chiller, supply outlet.
// On the demand side, there's also a bypass branch that is added by the FT by default
ASSERT_EQ(e.isSupply ? 3 : 4, idf_brlist.extensibleGroups().size()) << "Failed for " << e.plantName;
// Get the Chiller one
auto w_eg = idf_brlist.extensibleGroups()[1].cast<WorkspaceExtensibleGroup>();
WorkspaceObject idf_branch = w_eg.getTarget(BranchListExtensibleFields::BranchName).get();

// There should be only one equipment on the branch
ASSERT_EQ(1, idf_branch.extensibleGroups().size());
auto w_eg2 = idf_branch.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();

ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentName).get(), ch.nameString());
ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentInletNodeName).get(), e.inletNodeName);
ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentOutletNodeName).get(), e.outletNodeName);

WorkspaceObject idf_plant_op = p_->getTarget(PlantLoopFields::PlantEquipmentOperationSchemeName).get();
if (e.isSupply) {
// Should have created a Cooling Load one only
ASSERT_EQ(1, idf_plant_op.extensibleGroups().size());
auto w_eg = idf_plant_op.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
ASSERT_EQ("PlantEquipmentOperation:CoolingLoad",
w_eg.getString(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeObjectType).get());

// Get the Operation Scheme
auto op_scheme_ = w_eg.getTarget(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeName);
ASSERT_TRUE(op_scheme_);

// Get the Plant Equipment List of this CoolingLoad scheme
// There should only be one Load Range
ASSERT_EQ(1u, op_scheme_->extensibleGroups().size());

// Load range 1
w_eg = op_scheme_->extensibleGroups()[0].cast<WorkspaceExtensibleGroup>();
auto peq_list_ = w_eg.getTarget(PlantEquipmentOperation_HeatingLoadExtensibleFields::RangeEquipmentListName);
ASSERT_TRUE(peq_list_);

// Should have one equipment on it: CentralHeatPumpSystem
auto peqs = peq_list_->extensibleGroups();
ASSERT_EQ(1, peqs.size());
ASSERT_EQ("Chiller:Electric:ASHRAE205", peqs.front().getString(PlantEquipmentListExtensibleFields::EquipmentObjectType).get());
ASSERT_EQ(ch.nameString(), peqs.front().getString(PlantEquipmentListExtensibleFields::EquipmentName).get());

} else {
EXPECT_EQ(0, idf_plant_op.extensibleGroups().size());
}
}
}

// Not assigned to a Chilled Water Loop? not translated, it's required
ch.removeFromPlantLoop();
{
// Assigned to a Surface
Workspace w = ft.translateModel(m);

EXPECT_EQ(0, w.getObjectsByType(IddObjectType::Chiller_Electric_ASHRAE205).size());
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full FT testing

Comment on lines +316 to +408
TEST_F(EnergyPlusFixture, ReverseTranslator_ChillerElectricASHRAE205) {

ReverseTranslator rt;

Workspace w(StrictnessLevel::Minimal, IddFileType::EnergyPlus);

auto wo_zone = w.addObject(IdfObject(IddObjectType::Zone)).get();
wo_zone.setName("Basement");

auto woCh = w.addObject(IdfObject(IddObjectType::Chiller_Electric_ASHRAE205)).get();
woCh.setName("Chiller ASHRAE205");

openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor");
EXPECT_TRUE(exists(p));

EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName, openstudio::toString(p)));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod, "Cubic"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::RatedCapacity, "")); // Defaults to Autosize
EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::SizingFactor, 1.1));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator, "Zone"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName, ""));
EXPECT_TRUE(woCh.setPointer(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName, wo_zone.handle()));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName, ""));
EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, 0.0428));
EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, 0.0552));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChillerFlowMode, "ConstantFlow"));
EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate, 0.001));
EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate, 0.002));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::EndUseSubcategory, "Chiller"));

// Nodes not RT'ed
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName, "ChilledWater Inlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName, "ChilledWater Outlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName, "Condenser Inlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName, "Condenser Outlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName, "OilCooler Inlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName, "OilCooler Outlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName, "Auxiliary Inlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName, "Auxiliary Outlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName, "HeatRecovery Inlet Node"));
EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName, "HeatRecovery Outlet Node"));

Model m = rt.translateWorkspace(w);

auto chs = m.getConcreteModelObjects<ChillerElectricASHRAE205>();
ASSERT_EQ(1, chs.size());
auto& ch = chs.front();

ASSERT_TRUE(ch.name());
EXPECT_EQ("Chiller ASHRAE205", ch.nameString());
EXPECT_EQ("A205ExampleChiller.RS0001.a205.cbor", ch.representationFile().fileName());
EXPECT_EQ("Cubic", ch.performanceInterpolationMethod());
EXPECT_TRUE(ch.isRatedCapacityAutosized());
EXPECT_EQ(1.1, ch.sizingFactor());
EXPECT_EQ("Zone", ch.ambientTemperatureIndicator());
EXPECT_FALSE(ch.ambientTemperatureSchedule());
ASSERT_TRUE(ch.ambientTemperatureZone());
EXPECT_EQ("Basement", ch.ambientTemperatureZone()->nameString());
EXPECT_FALSE(ch.ambientTemperatureOutdoorAirNodeName());

EXPECT_FALSE(ch.isChilledWaterMaximumRequestedFlowRateAutosized());
ASSERT_TRUE(ch.chilledWaterMaximumRequestedFlowRate());
EXPECT_EQ(0.0428, ch.chilledWaterMaximumRequestedFlowRate().get());

EXPECT_FALSE(ch.isCondenserMaximumRequestedFlowRateAutosized());
ASSERT_TRUE(ch.condenserMaximumRequestedFlowRate());
EXPECT_EQ(0.0552, ch.condenserMaximumRequestedFlowRate().get());

EXPECT_EQ("ConstantFlow", ch.chillerFlowMode());

ASSERT_TRUE(ch.oilCoolerDesignFlowRate());
EXPECT_EQ(0.001, ch.oilCoolerDesignFlowRate().get());
ASSERT_TRUE(ch.auxiliaryCoolingDesignFlowRate());
EXPECT_EQ(0.002, ch.auxiliaryCoolingDesignFlowRate().get());

EXPECT_EQ("Chiller", ch.endUseSubcategory());

EXPECT_FALSE(ch.chilledWaterLoop());
EXPECT_FALSE(ch.condenserWaterLoop());
EXPECT_FALSE(ch.heatRecoveryLoop());
EXPECT_FALSE(ch.oilCoolerLoop());
EXPECT_FALSE(ch.auxiliaryLoop());
EXPECT_FALSE(ch.chilledWaterInletNode());
EXPECT_FALSE(ch.chilledWaterOutletNode());
EXPECT_FALSE(ch.condenserInletNode());
EXPECT_FALSE(ch.condenserOutletNode());
EXPECT_FALSE(ch.heatRecoveryInletNode());
EXPECT_FALSE(ch.heatRecoveryOutletNode());
EXPECT_FALSE(ch.oilCoolerInletNode());
EXPECT_FALSE(ch.oilCoolerOutletNode());
EXPECT_FALSE(ch.auxiliaryInletNode());
EXPECT_FALSE(ch.auxiliaryOutletNode());
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RT testing

Copy link
Collaborator

@joseph-robertson joseph-robertson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty minimal review/questions from me - I think @kbenne would have more meaningful feedback re connections/loops, etc. One general thought I have is around eventual update for when heat recovery is available; this implementation wouldn't preclude future updates would it?

\type choice
\key Linear
\key Cubic
\required-field
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using requireds 👍

IdfObject idfObject = createRegisterAndNameIdfObject(openstudio::IddObjectType::Chiller_Electric_ASHRAE205, modelObject);

// Representation File Name: Required String
idfObject.setString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName, openstudio::toString(filePath));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anything ensure that the representation file is in the correct format?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. Just that is has a cbor extension. The parsing is not even done inside E+ directly but in an third_party lib. It'll throw at that point.

case openstudio::IddObjectType::Chiller_Electric_ASHRAE205: {
modelObject = translateChillerElectricASHRAE205(workspaceObject);
break;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was sort of under the impression that we tended to not RT HVAC components?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh. There are others. I don't RT the node connections, only the rest.

model/offset_tests.osm
model/ParkUnder_Retail_Office_C2.osm
model/ASHRAECourthouse.osm
model/A205ExampleChiller.RS0001.a205.cbor
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was this generated/obtained? Would we ever foresee it needing to get updated in the future?

Copy link
Collaborator Author

@jmarrec jmarrec Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{"AvailabilityManagerScheduledOff", "Availability Manager Scheduled Off", "schedule", false, "Availability", 0.0, 1.0},
{"CentralHeatPumpSystem", "Ancillary Operation", "ancillaryOperationSchedule", false, "Availability", 0.0, 1.0},
{"CentralHeatPumpSystemModule", "Chiller Heater Modules Control", "chillerHeaterModulesControlSchedule", false, "Availability", 0.0, 1.0},
{"ChillerElectricASHRAE205", "Ambient Temperature", "ambientTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is availability schedule absorbed into the representation file?

@jmarrec
Copy link
Collaborator Author

jmarrec commented Sep 28, 2022

Pretty minimal review/questions from me - I think @kbenne would have more meaningful feedback re connections/loops, etc. One general thought I have is around eventual update for when heat recovery is available; this implementation wouldn't preclude future updates would it?

As I said in #4687 (comment) I actually did all the the implementation and testing with heat Recovery Connection enabled (this is the only one that is not supported by E+), and set to be the tertiary loop connection. I disabled it at model time last in 1211428#diff-d88d3c168ac09d7579c9e21a71689ea7bef7c0a1ec822e1c2624d20ac04ed47cR62-R63. That commit can be reverted (or that constexpr changed to true and you're good to go)

@jmarrec
Copy link
Collaborator Author

jmarrec commented Sep 28, 2022

I'm trying to kill the V22.2.0-IOFreeze branch sicne you started a different one for 22.2.0 official, so merging now

@jmarrec jmarrec merged commit 634963c into v22.2.0-IOFreeze Sep 28, 2022
@jmarrec jmarrec deleted the 4656_ChillerElectricASHRAE205 branch September 28, 2022 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E+ 22.2.0: Wrap Chiller:Electric:ASHRAE205

2 participants